use tokio::sync::{mpsc, oneshot};
use tonic::{async_trait, transport::Server};

use crate::types::{self, channel};

use super::iblog_proto::{self, *};

struct IblogImpl {
    db_tx: mpsc::Sender<(channel::Request, oneshot::Sender<channel::Response>)>,
}

impl IblogImpl {
    fn new(db_tx: mpsc::Sender<(channel::Request, oneshot::Sender<channel::Response>)>) -> Self {
        Self { db_tx }
    }
}

#[async_trait]
impl iblog_server::Iblog for IblogImpl {
    async fn blog_add(
        &self,
        request: tonic::Request<iblog_proto::Blog>,
    ) -> Result<tonic::Response<iblog_proto::Id>, tonic::Status> {
        let blog = request.into_inner();

        println!("blog_add: {:?}", &blog);

        let (rsp_tx, rsp_rx) = oneshot::channel::<channel::Response>();

        self.db_tx
            .send((
                channel::Request::BlogAdd(types::blog::AddRequest {
                    title: blog.title,
                    content: blog.content,
                    tags: blog.tags,
                    author: blog.author,
                    author_id: blog.author_id,
                }),
                rsp_tx,
            ))
            .await;

        match rsp_rx.await.unwrap() {
            channel::Response::BlogAdd(id) => {
                Ok(tonic::Response::new(iblog_proto::Id { value: id }))
            }
            channel::Response::Error(err) => Err(tonic::Status::internal(err)),
            _ => unreachable!(),
        }
    }

    async fn blog_update(
        &self,
        request: tonic::Request<iblog_proto::Blog>,
    ) -> Result<tonic::Response<iblog_proto::Ok>, tonic::Status> {
        let blog = request.into_inner();

        let (rsp_tx, rsp_rx) = oneshot::channel::<channel::Response>();

        self.db_tx
            .send((
                channel::Request::BlogUpdate(types::blog::UpdateRequest {
                    id: blog.id,
                    title: blog.title,
                    content: blog.content,
                    tags: blog.tags,
                }),
                rsp_tx,
            ))
            .await;

        match rsp_rx.await.unwrap() {
            channel::Response::Ok() => Ok(tonic::Response::new(iblog_proto::Ok {})),
            channel::Response::Error(err) => Err(tonic::Status::internal(err)),
            _ => unreachable!(),
        }
    }

    async fn blog_get(
        &self,
        request: tonic::Request<iblog_proto::Id>,
    ) -> Result<tonic::Response<iblog_proto::Blog>, tonic::Status> {
        let id = request.into_inner().value;

        let (rsp_tx, rsp_rx) = oneshot::channel::<channel::Response>();

        self.db_tx
            .send((channel::Request::BlogGet(id), rsp_tx))
            .await;

        match rsp_rx.await.unwrap() {
            channel::Response::BlogGet(blog) => Ok(tonic::Response::new(iblog_proto::Blog {
                id: blog.id,
                title: blog.title,
                content: blog.content,
                tags: blog.tags,
                author: blog.author,
                author_id: blog.author_id,
                created_at: blog.created_at.to_string(),
                views: blog.views,
                stars: blog.stars,
            })),
            channel::Response::Error(err) => Err(tonic::Status::internal(err)),
            _ => unreachable!(),
        }
    }

    async fn blogs_get(
        &self,
        request: tonic::Request<iblog_proto::BlogsGetRequest>,
    ) -> Result<tonic::Response<iblog_proto::BlogsGetResponse>, tonic::Status> {
        let request = request.into_inner();

        let (rsp_tx, rsp_rx) = oneshot::channel::<channel::Response>();

        self.db_tx
            .send((
                channel::Request::BlogsGet(types::blog::GetMutipleRequest {
                    id: request.id,
                    count: request.count,
                }),
                rsp_tx,
            ))
            .await;

        match rsp_rx.await.unwrap() {
            channel::Response::BlogsGet(rsp) => {
                Ok(tonic::Response::new(iblog_proto::BlogsGetResponse {
                    total_count: rsp.total_count,
                    blog_count: rsp.blog_count,
                    blogs: rsp
                        .blogs
                        .into_iter()
                        .map(|blog| iblog_proto::Blog {
                            id: blog.id,
                            title: blog.title,
                            content: blog.content,
                            tags: blog.tags,
                            author: blog.author,
                            author_id: blog.author_id,
                            created_at: blog.created_at.to_string(),
                            views: blog.views,
                            stars: blog.stars,
                        })
                        .collect(),
                }))
            }
            channel::Response::Error(err) => Err(tonic::Status::internal(err)),
            _ => unreachable!(),
        }
    }

    async fn blog_delete(
        &self,
        request: tonic::Request<iblog_proto::Id>,
    ) -> Result<tonic::Response<iblog_proto::Ok>, tonic::Status> {
        let id = request.into_inner().value;

        let (rsp_tx, rsp_rx) = oneshot::channel::<channel::Response>();

        self.db_tx
            .send((channel::Request::BlogDelete(id), rsp_tx))
            .await;

        match rsp_rx.await.unwrap() {
            channel::Response::Ok() => Ok(tonic::Response::new(iblog_proto::Ok {})),
            channel::Response::Error(err) => Err(tonic::Status::internal(err)),
            _ => unreachable!(),
        }
    }

    async fn blog_view_inc(
        &self,
        request: tonic::Request<iblog_proto::Id>,
    ) -> Result<tonic::Response<iblog_proto::Views>, tonic::Status> {
        let id = request.into_inner().value;

        let (rsp_tx, rsp_rx) = oneshot::channel::<channel::Response>();

        self.db_tx
            .send((channel::Request::BlogViewInc(id), rsp_tx))
            .await;

        match rsp_rx.await.unwrap() {
            channel::Response::Views(views) => {
                Ok(tonic::Response::new(iblog_proto::Views { value: views }))
            }
            channel::Response::Error(err) => Err(tonic::Status::internal(err)),
            _ => unreachable!(),
        }
    }

    async fn blog_star_inc(
        &self,
        request: tonic::Request<iblog_proto::Id>,
    ) -> Result<tonic::Response<iblog_proto::Stars>, tonic::Status> {
        let id = request.into_inner().value;

        let (rsp_tx, rsp_rx) = oneshot::channel::<channel::Response>();

        self.db_tx
            .send((channel::Request::BlogStarInc(id), rsp_tx))
            .await;

        match rsp_rx.await.unwrap() {
            channel::Response::Views(stars) => {
                Ok(tonic::Response::new(iblog_proto::Stars { value: stars }))
            }
            channel::Response::Error(err) => Err(tonic::Status::internal(err)),
            _ => unreachable!(),
        }
    }

    async fn blog_star_dec(
        &self,
        request: tonic::Request<iblog_proto::Id>,
    ) -> Result<tonic::Response<iblog_proto::Stars>, tonic::Status> {
        let id = request.into_inner().value;

        let (rsp_tx, rsp_rx) = oneshot::channel::<channel::Response>();

        self.db_tx
            .send((channel::Request::BlogStarDec(id), rsp_tx))
            .await;

        match rsp_rx.await.unwrap() {
            channel::Response::Views(stars) => {
                Ok(tonic::Response::new(iblog_proto::Stars { value: stars }))
            }
            channel::Response::Error(err) => Err(tonic::Status::internal(err)),
            _ => unreachable!(),
        }
    }

    async fn idea_add(
        &self,
        request: tonic::Request<iblog_proto::Idea>,
    ) -> Result<tonic::Response<iblog_proto::Id>, tonic::Status> {
        let idea = request.into_inner();

        let (rsp_tx, rsp_rx) = oneshot::channel::<channel::Response>();

        self.db_tx
            .send((
                channel::Request::IdeaAdd(types::idea::AddRequest {
                    title: idea.title,
                    content: idea.content,
                    tags: idea.tags,
                }),
                rsp_tx,
            ))
            .await;

        match rsp_rx.await.unwrap() {
            channel::Response::IdeaAdd(id) => {
                Ok(tonic::Response::new(iblog_proto::Id { value: id }))
            }
            channel::Response::Error(err) => Err(tonic::Status::internal(err)),
            _ => unreachable!(),
        }
    }

    async fn idea_update(
        &self,
        request: tonic::Request<iblog_proto::Idea>,
    ) -> Result<tonic::Response<iblog_proto::Ok>, tonic::Status> {
        let idea = request.into_inner();

        let (rsp_tx, rsp_rx) = oneshot::channel::<channel::Response>();

        self.db_tx
            .send((
                channel::Request::IdeaUpdate(types::idea::UpdateRequest {
                    id: idea.id,
                    title: idea.title,
                    content: idea.content,
                    tags: idea.tags,
                }),
                rsp_tx,
            ))
            .await;

        match rsp_rx.await.unwrap() {
            channel::Response::Ok() => Ok(tonic::Response::new(iblog_proto::Ok {})),
            channel::Response::Error(err) => Err(tonic::Status::internal(err)),
            _ => unreachable!(),
        }
    }

    async fn idea_get(
        &self,
        request: tonic::Request<iblog_proto::Id>,
    ) -> Result<tonic::Response<iblog_proto::Idea>, tonic::Status> {
        let id = request.into_inner().value;

        let (rsp_tx, rsp_rx) = oneshot::channel::<channel::Response>();

        self.db_tx
            .send((channel::Request::IdeaGet(id), rsp_tx))
            .await;

        match rsp_rx.await.unwrap() {
            channel::Response::IdeaGet(idea) => Ok(tonic::Response::new(iblog_proto::Idea {
                id: idea.id,
                title: idea.title,
                content: idea.content,
                tags: idea.tags,
                created_at: idea.created_at.to_string(),
                views: idea.views,
                stars: idea.stars,
            })),
            channel::Response::Error(err) => Err(tonic::Status::internal(err)),
            _ => unreachable!(),
        }
    }

    async fn ideas_get(
        &self,
        request: tonic::Request<iblog_proto::IdeasGetRequest>,
    ) -> Result<tonic::Response<iblog_proto::IdeasGetResponse>, tonic::Status> {
        let request = request.into_inner();

        let (rsp_tx, rsp_rx) = oneshot::channel::<channel::Response>();

        self.db_tx
            .send((
                channel::Request::IdeasGet(types::idea::GetMutipleRequest {
                    id: request.id,
                    count: request.count,
                }),
                rsp_tx,
            ))
            .await;

        match rsp_rx.await.unwrap() {
            channel::Response::IdeasGet(rsp) => {
                Ok(tonic::Response::new(iblog_proto::IdeasGetResponse {
                    total_count: rsp.total_count,
                    idea_count: rsp.idea_count,
                    ideas: rsp
                        .ideas
                        .into_iter()
                        .map(|idea| iblog_proto::Idea {
                            id: idea.id,
                            title: idea.title,
                            content: idea.content,
                            tags: idea.tags,
                            created_at: idea.created_at.to_string(),
                            views: idea.views,
                            stars: idea.stars,
                        })
                        .collect(),
                }))
            }
            channel::Response::Error(err) => Err(tonic::Status::internal(err)),
            _ => unreachable!(),
        }
    }

    async fn idea_delete(
        &self,
        request: tonic::Request<iblog_proto::Id>,
    ) -> Result<tonic::Response<iblog_proto::Ok>, tonic::Status> {
        let id = request.into_inner().value;

        let (rsp_tx, rsp_rx) = oneshot::channel::<channel::Response>();

        self.db_tx
            .send((channel::Request::IdeaDelete(id), rsp_tx))
            .await;

        match rsp_rx.await.unwrap() {
            channel::Response::Ok() => Ok(tonic::Response::new(iblog_proto::Ok {})),
            channel::Response::Error(err) => Err(tonic::Status::internal(err)),
            _ => unreachable!(),
        }
    }

    async fn idea_view_inc(
        &self,
        request: tonic::Request<iblog_proto::Id>,
    ) -> Result<tonic::Response<iblog_proto::Views>, tonic::Status> {
        let id = request.into_inner().value;

        let (rsp_tx, rsp_rx) = oneshot::channel::<channel::Response>();

        self.db_tx
            .send((channel::Request::IdeaViewInc(id), rsp_tx))
            .await;

        match rsp_rx.await.unwrap() {
            channel::Response::Views(views) => {
                Ok(tonic::Response::new(iblog_proto::Views { value: views }))
            }
            channel::Response::Error(err) => Err(tonic::Status::internal(err)),
            _ => unreachable!(),
        }
    }

    async fn idea_star_inc(
        &self,
        request: tonic::Request<iblog_proto::Id>,
    ) -> Result<tonic::Response<iblog_proto::Stars>, tonic::Status> {
        let id = request.into_inner().value;

        let (rsp_tx, rsp_rx) = oneshot::channel::<channel::Response>();

        self.db_tx
            .send((channel::Request::IdeaStarInc(id), rsp_tx))
            .await;

        match rsp_rx.await.unwrap() {
            channel::Response::Views(stars) => {
                Ok(tonic::Response::new(iblog_proto::Stars { value: stars }))
            }
            channel::Response::Error(err) => Err(tonic::Status::internal(err)),
            _ => unreachable!(),
        }
    }

    async fn idea_star_dec(
        &self,
        request: tonic::Request<iblog_proto::Id>,
    ) -> Result<tonic::Response<iblog_proto::Stars>, tonic::Status> {
        let id = request.into_inner().value;

        let (rsp_tx, rsp_rx) = oneshot::channel::<channel::Response>();

        self.db_tx
            .send((channel::Request::IdeaStarDec(id), rsp_tx))
            .await;

        match rsp_rx.await.unwrap() {
            channel::Response::Views(stars) => {
                Ok(tonic::Response::new(iblog_proto::Stars { value: stars }))
            }
            channel::Response::Error(err) => Err(tonic::Status::internal(err)),
            _ => unreachable!(),
        }
    }
}

pub async fn start(db_tx: mpsc::Sender<(channel::Request, oneshot::Sender<channel::Response>)>) {
    let addr = "0.0.0.0:9001".parse().unwrap();
    let iblog_impl = IblogImpl::new(db_tx);

    let iblog_service = iblog_proto::iblog_server::IblogServer::new(iblog_impl);

    Server::builder()
        .add_service(iblog_service)
        .serve(addr)
        .await
        .unwrap();
}
